using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;

namespace Microsoft.SharedSource.SqlServer.MDDE.Components
{
    [
        System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1705:LongAcronymsShouldBePascalCased"),
        SupportedComponentType("{A1DF9F6D-8EE4-4EF0-BB2E-D526130D7A7B}"),
        SupportedComponentType("{E2568105-9550-4F71-A638-B7FE42E66922}"),
        //SupportedComponentType("{C60ACAD1-9BE8-46B3-87DA-70E59EADEA46}")
    ]
    public class MDDEDataFlowDestination : MDDEDataFlowComponent
    {
        #region Member Variables

        private XmlNodeList changedProperties = null;
        private IDTSCustomPropertyCollection100 customProperties = null;
        private XmlNodeList customMappings = null;
        private bool preservePreviousMappings = true;

        //private Package originalPackage = new Package();
        IDTSComponentMetaData100 component = null;

        #endregion

        public MDDEDataFlowDestination(Package package, Object component, string name, string uri)
            : base(package, component, name, uri)
        {
        }

        #region Overridden Methods

        public override void GetChangeXml(MDDEComponent subject, XmlDocument changeXml)
        {
            #region Set Values

            if (subject == null)
                throw new ArgumentNullException("Subject can not NULL");

            if (!(subject is MDDEDataFlowDestination))
                throw new ArgumentException("Subject is not of type MDDEDataFlowDestination");

            component = (IDTSComponentMetaData100)innerComponent;
            IDTSComponentMetaData100 referenceComponent = (IDTSComponentMetaData100)subject.InnerComponent;

            IDTSCustomPropertyCollection100 componentPropertyCollection = (IDTSCustomPropertyCollection100)component.CustomPropertyCollection;
            IDTSCustomPropertyCollection100 referenceComponentPropertyCollection = (IDTSCustomPropertyCollection100)referenceComponent.CustomPropertyCollection;

            XmlNode componentNode = null;
            componentNode = changeXml.CreateElement("Component");

            #endregion

            #region Compare properties of Component with subject and create Properties' Xml

            for (int propertyIndex = 0; propertyIndex < componentPropertyCollection.Count; propertyIndex++)
            {
                if (componentPropertyCollection[propertyIndex].Name.CompareTo("Name") == 0)
                    continue;

                IDTSCustomProperty100 componentProperty = componentPropertyCollection[propertyIndex];
                IDTSCustomProperty100 referenceComponentProperty = referenceComponentPropertyCollection[propertyIndex];

                string componentPropertyValue = String.Empty;
                if (componentProperty.Value != null)
                    componentPropertyValue = componentProperty.Value.ToString();

                string referenceComponentPropertyValue = String.Empty;
                if (referenceComponentProperty.Value != null)
                    referenceComponentPropertyValue = referenceComponentProperty.Value.ToString();

                if (componentPropertyValue != referenceComponentPropertyValue)
                {
                    UpdateNodeProperties(changeXml, componentNode, componentProperty.Name, componentPropertyValue);
                }
            }

            #endregion

            #region Extract Custom Mappings from Component

            IDTSInput100 input = component.InputCollection[0];
            IDTSVirtualInput100 virtualInput = input.GetVirtualInput();
            IDTSVirtualInputColumnCollection100 virtualInputColumnCollection = virtualInput.VirtualInputColumnCollection;

            bool inputColumnUsed;
            int externalMetadataColumnID = 0;

            UpdateNodeMappings(changeXml, componentNode, false);

            foreach (IDTSVirtualInputColumn100 virtualColumn in virtualInputColumnCollection)
            {
                inputColumnUsed = false;

                #region Check if virtual column is present in Input Collection (Signifies that Input Column is used)

                foreach (IDTSInputColumn100 inputColumn in input.InputColumnCollection)
                {
                    if (inputColumn.Name == virtualColumn.Name)
                    {
                        externalMetadataColumnID = inputColumn.ExternalMetadataColumnID;
                        inputColumnUsed = true;
                        break;
                    }
                }

                #endregion

                if (inputColumnUsed)
                {
                    #region Check to which external column the input column is mapped

                    string externalColumnName = String.Empty;
                    foreach (IDTSExternalMetadataColumn100 externalColumn in input.ExternalMetadataColumnCollection)
                    {
                        if (externalColumn.ID == externalMetadataColumnID)
                        {
                            externalColumnName = externalColumn.Name;
                            break;
                        }
                    }

                    #endregion

                    if (externalColumnName != virtualColumn.Name)
                    {
                        UpdateNodeMapping(changeXml, componentNode, false,
                            "True", virtualColumn.Name, externalColumnName);
                    }
                }
                else
                {
                    UpdateNodeMapping(changeXml, componentNode, false,
                        "False", virtualColumn.Name, String.Empty);
                }
            }

            #endregion

            #region Update Changes Xml

            AddAttribute(changeXml, componentNode, "Name", this.name);
            AddAttribute(changeXml, componentNode, "URL", this.uri);
            AddAttribute(changeXml, componentNode, "RowsetDescriptor", "");

            changeXml.FirstChild.AppendChild(componentNode);

            #endregion
        }

        public override bool ApplyChanges(XmlDocument changeXml, MDDEExecutionDAG originalPackage)
        {
            try
            {
                #region Set Values

                XmlNode componentChangeXml =
                    changeXml.SelectSingleNode("//PackageChanges/Component[@URL='" + this.uri + "']");

                //string packageXml = null;
                //package.SaveToXML(out packageXml, null);
                //originalPackage.LoadFromXML(packageXml, null);

                //if (innerComponent == null)
                //    return false;

                //if (!(innerComponent is IDTSComponentMetaData100))
                //    return false;

                component = (IDTSComponentMetaData100)innerComponent;

                #endregion

                #region Read Change Xml

                if (componentChangeXml != null)
                {
                    if (!ReadChangeXml(componentChangeXml))
                        return false;
                }

                #endregion

                #region Apply Properties and Verify If Sufficient Property Values' Are Available

                if (!ApplyProperties())
                    return false;

                #endregion

                #region Add Connection Manager to Runtime Collection

                string connectionManagerID = component.RuntimeConnectionCollection[0].ConnectionManagerID;

                if (String.IsNullOrEmpty(connectionManagerID))
                    return false;

                component.RuntimeConnectionCollection[0].ConnectionManager = DtsConvert.ToConnectionManager90(this.package.Connections[connectionManagerID]);

                #endregion

                #region Clear Invalid Input Columns

                component.RemoveInvalidInputColumns();

                #endregion

                #region Reinitialize Component

                IDTSDesigntimeComponent100 designTimeComponent = component.Instantiate();

                designTimeComponent.AcquireConnections(null);

                DTSValidationStatus validationStatus = designTimeComponent.Validate();
                if (validationStatus == DTSValidationStatus.VS_ISBROKEN)
                {
                    designTimeComponent.ReleaseConnections();
                    return false;
                }

                designTimeComponent.ReinitializeMetaData();
                designTimeComponent.ReleaseConnections();

                #endregion

                #region Get Virtual Input Collections

                IDTSInput100 input = component.InputCollection[0];
                IDTSVirtualInput100 virutalInput = input.GetVirtualInput();

                IDTSVirtualInputColumnCollection100 virtualInputColumnCollection
                    = virutalInput.VirtualInputColumnCollection;

                #endregion

                #region Map Input Columns with External Metadata Columns

                #region Map on the basis of Names

                CreateInputCollectionAndMapByName(designTimeComponent, input,
                    virutalInput, virtualInputColumnCollection);

                #endregion

                #region Restore Previous Mapping Settings based on User Preference

                if (preservePreviousMappings)
                {
                    IDTSComponentMetaData100 originalComponent =
                        (originalPackage.GetMDDEComponentByUri(uri)).InnerComponent as IDTSComponentMetaData100;
                    RestorePreviousMappings(designTimeComponent, input,
                        virutalInput, virtualInputColumnCollection, originalComponent);
                }

                #endregion

                #region Override Mappings with New Custom Mappings

                if (customMappings != null)
                {
                    ApplyCustomMappings(designTimeComponent, input,
                        virutalInput, virtualInputColumnCollection);
                }

                #endregion

                #endregion

                return true;
            }

            catch
            {
                // TODO: Add to Log
                return false;
            }
        }

        public override bool Validate()
        {
            try
            {
                #region Add Connection Manager to Runtime Collection

                string connectionManagerID
                    = ((IDTSComponentMetaData100)innerComponent).RuntimeConnectionCollection[0].ConnectionManagerID;

                if (String.IsNullOrEmpty(connectionManagerID))
                    return false;

                ((IDTSComponentMetaData100)innerComponent).RuntimeConnectionCollection[0].ConnectionManager
                    = DtsConvert.ToConnectionManager90(this.package.Connections[connectionManagerID]);

                #endregion

                IDTSDesigntimeComponent100 designTimeComponent = ((IDTSComponentMetaData100)innerComponent).Instantiate();

                designTimeComponent.AcquireConnections(null);
                DTSValidationStatus validationStatus = designTimeComponent.Validate();
                designTimeComponent.ReleaseConnections();

                if (validationStatus == DTSValidationStatus.VS_ISVALID)
                    return true;
                else
                    return false;
            }

            catch
            {
                // TODO: Add to Log
                return false;
            }
        }

        public override XmlDocument GetComponentTree()
        {
            XmlDocument xComponent = base.GetComponentTree();

            XmlElement xRoot = xComponent.DocumentElement;

            if (InnerDataFlowComponent.InputCollection.Count > 0)
            {
                // Proceed only if we are using the external metadata.
                if (InnerDataFlowComponent.InputCollection[0].
                      ExternalMetadataColumnCollection.IsUsed)
                {
                    IDTSExternalMetadataColumnCollection100 extMDColl =
                        InnerDataFlowComponent.InputCollection[0].ExternalMetadataColumnCollection;

                    XmlElement xnodeMappings =
                        AppendChildElement(xRoot, "Mappings", null);

                    foreach (IDTSInputColumn100 inputCol in
                        InnerDataFlowComponent.InputCollection[0].InputColumnCollection)
                    {
                        bool usage = false;
                        foreach (IDTSExternalMetadataColumn100 ext in extMDColl)
                        {
                            if (ext.ID == inputCol.ExternalMetadataColumnID)
                            {
                                AppendChildElement(xnodeMappings, "Mapping",
                                    new string[]{ "Usage", "True", "SourceColumn", inputCol.Name,
                                        "DestinationColumn", ext.Name});
                                usage = true;
                                break;
                            }
                        }
                        if (!usage)
                        {
                            AppendChildElement(xnodeMappings, "Mapping",
                                new string[]{ "Usage", "False", "SourceColumn", inputCol.Name,
                                    "DestinationColumn", string.Empty});
                        }
                    }

                    if (!xnodeMappings.HasChildNodes)
                        xnodeMappings.ParentNode.RemoveChild(xnodeMappings);
                }
            }

            return xComponent;
        }

        #endregion

        #region Helper Methods

        private bool ReadChangeXml(XmlNode changeXml)
        {
            if (changeXml.SelectSingleNode("Properties") != null)
                changedProperties = changeXml["Properties"].ChildNodes;

            if (component.CustomPropertyCollection == null)
                return false;

            customProperties = (IDTSCustomPropertyCollection100)component.CustomPropertyCollection;

            if (customProperties == null)
                return false;

            if (changeXml.SelectSingleNode("Mappings") != null)
            {
                if (changeXml["Mappings"].ChildNodes.Count > 0)
                    customMappings = changeXml["Mappings"].ChildNodes;

                if (changeXml["Mappings"].Attributes["PreservePreviousMappings"] != null)
                    preservePreviousMappings = Convert.ToBoolean(
                        changeXml["Mappings"].Attributes["PreservePreviousMappings"].Value);
            }

            return true;
        }

        private bool ApplyProperties()
        {
            if (changedProperties != null)
            {
                foreach (XmlNode changedProperty in changedProperties)
                    customProperties[changedProperty.Attributes["Name"].Value].Value = this.ConvertToCompatibleType(changedProperty.Attributes["Value"].Value,
                        customProperties[changedProperty.Attributes["Name"].Value].Value.GetType().Name);
            }

            if (component.Instantiate().Validate()
                == DTSValidationStatus.VS_ISBROKEN)
                return false;
            else
                return true;
        }

        private void ApplyCustomMappings(IDTSDesigntimeComponent100 designTimeComponent, IDTSInput100 input, IDTSVirtualInput100 virutalInput, IDTSVirtualInputColumnCollection100 virtualInputColumnCollection)
        {
            foreach (XmlNode componentMapping in customMappings)
            {
                bool usage = Convert.ToBoolean(componentMapping.Attributes["Usage"].Value.ToString());
                string inputColumnName = componentMapping.Attributes["SourceColumn"].Value.ToString();
                string externalColumnName = String.Empty;
                if (usage)
                    externalColumnName = componentMapping.Attributes["DestinationColumn"].Value.ToString(); ;

                #region Find Columns' Presence in New Package

                bool virtualInputColumnPresent = false;
                bool inputColumnPresent = false;
                bool externalColumnPresent = false;
                int columnIndex;
                int virtualInputColumnID = 0;

                for (columnIndex = 0; columnIndex < virtualInputColumnCollection.Count;
                    columnIndex++)
                {
                    if (inputColumnName
                            == virtualInputColumnCollection[columnIndex].Name)
                    {
                        virtualInputColumnPresent = true;
                        virtualInputColumnID = virtualInputColumnCollection[columnIndex].ID;
                        break;
                    }
                }

                for (columnIndex = 0; columnIndex < input.InputColumnCollection.Count;
                    columnIndex++)
                {
                    if (inputColumnName
                            == input.InputColumnCollection[columnIndex].Name)
                    {
                        inputColumnPresent = true;
                        break;
                    }
                }

                for (columnIndex = 0; columnIndex < input.ExternalMetadataColumnCollection.Count;
                    columnIndex++)
                {
                    if (externalColumnName
                            == input.ExternalMetadataColumnCollection[columnIndex].Name)
                    {
                        externalColumnPresent = true;
                        break;
                    }
                }

                #endregion

                #region Input Column to be Used - Map with External Column

                if (usage)
                {
                    if (virtualInputColumnPresent && externalColumnPresent)
                    {
                        if (!inputColumnPresent)
                        {
                            designTimeComponent.SetUsageType(input.ID, virutalInput,
                                virtualInputColumnCollection[inputColumnName].LineageID,
                                DTSUsageType.UT_READONLY);
                        }

                        designTimeComponent.MapInputColumn(input.ID, input.InputColumnCollection[inputColumnName].ID,
                            input.ExternalMetadataColumnCollection[externalColumnName].ID);
                    }
                }

                #endregion

                #region Input Column not to be Used - Remove if present in Input Collection

                else
                {
                    if (inputColumnPresent)
                    {
                        designTimeComponent.SetUsageType(input.ID, virutalInput,
                                virtualInputColumnCollection[inputColumnName].LineageID,
                                DTSUsageType.UT_IGNORED);
                    }
                }

                #endregion
            }
        }

        private void RestorePreviousMappings(IDTSDesigntimeComponent100 designTimeComponent,
            IDTSInput100 input, IDTSVirtualInput100 virutalInput,
            IDTSVirtualInputColumnCollection100 virtualInputColumnCollection,
            IDTSComponentMetaData100 originalComponent)
        {
            //ObjectType objectType = ObjectType.Package;
            //IDTSComponentMetaData100 originalComponent
            //    = (IDTSComponentMetaData100)this.GetComponent(this.originalPackage, uri, ref objectType);

            IDTSInput100 originalInput = originalComponent.InputCollection[0];
            IDTSVirtualInput100 originalVirutalInput = originalInput.GetVirtualInput();

            IDTSVirtualInputColumnCollection100 originalVirtualInputColumnCollection
                = originalVirutalInput.VirtualInputColumnCollection;

            foreach (IDTSVirtualInputColumn100 originalVirtualInputColumn in originalVirtualInputColumnCollection)
            {
                #region Find if Original Input Column is Mapped to any Original External Column

                IDTSExternalMetadataColumn100 originalExternalColumn = null;
                bool originalInputColumnUsed = false;
                int inputColumnExternalMetadataID = 0;

                for (int originalInputColumnIndex = 0;
                    originalInputColumnIndex < originalInput.InputColumnCollection.Count;
                    originalInputColumnIndex++)
                {
                    if (originalInput.InputColumnCollection[originalInputColumnIndex].Name
                            == originalVirtualInputColumn.Name)
                    {
                        inputColumnExternalMetadataID
                            = originalInput.InputColumnCollection[originalInputColumnIndex].ExternalMetadataColumnID;
                        break;
                    }
                }

                if (inputColumnExternalMetadataID != 0)
                {
                    for (int originalExternalColumnIndex = 0;
                            originalExternalColumnIndex < originalInput.ExternalMetadataColumnCollection.Count;
                            originalExternalColumnIndex++)
                    {
                        if (originalInput.ExternalMetadataColumnCollection[originalExternalColumnIndex].ID
                                == inputColumnExternalMetadataID)
                        {
                            originalExternalColumn = originalInput.ExternalMetadataColumnCollection[originalExternalColumnIndex];
                            originalInputColumnUsed = true;
                            break;
                        }
                    }
                }

                #endregion

                #region Find Columns' Presence in New Package

                bool virtualInputColumnPresent = false;
                bool inputColumnPresent = false;
                bool externalColumnPresent = false;
                int columnIndex;
                int virtualInputColumnID = 0;

                for (columnIndex = 0; columnIndex < virtualInputColumnCollection.Count;
                    columnIndex++)
                {
                    if (originalVirtualInputColumn.Name
                            == virtualInputColumnCollection[columnIndex].Name)
                    {
                        virtualInputColumnPresent = true;
                        virtualInputColumnID = virtualInputColumnCollection[columnIndex].ID;
                        break;
                    }
                }

                for (columnIndex = 0; columnIndex < input.InputColumnCollection.Count;
                    columnIndex++)
                {
                    if (originalVirtualInputColumn.Name
                            == input.InputColumnCollection[columnIndex].Name)
                    {
                        inputColumnPresent = true;
                        break;
                    }
                }

                if (originalInputColumnUsed)
                {
                    for (columnIndex = 0; columnIndex < input.ExternalMetadataColumnCollection.Count;
                        columnIndex++)
                    {
                        if (originalExternalColumn.Name
                                == input.ExternalMetadataColumnCollection[columnIndex].Name)
                        {
                            externalColumnPresent = true;
                            break;
                        }
                    }
                }

                #endregion

                #region Input Column was Used in Original Package

                if (originalInputColumnUsed)
                {
                    if (virtualInputColumnPresent && externalColumnPresent)
                    {
                        if (!inputColumnPresent)
                        {
                            designTimeComponent.SetUsageType(input.ID, virutalInput,
                                virtualInputColumnCollection[originalVirtualInputColumn.Name].LineageID,
                                DTSUsageType.UT_READONLY);
                        }

                        designTimeComponent.MapInputColumn(input.ID, input.InputColumnCollection[originalVirtualInputColumn.Name].ID,
                            input.ExternalMetadataColumnCollection[originalExternalColumn.Name].ID);
                    }
                }

                #endregion

                #region Input Column was Not Used in Original Package

                else
                {
                    if (inputColumnPresent)
                    {
                        designTimeComponent.SetUsageType(input.ID, virutalInput,
                                virtualInputColumnCollection[originalVirtualInputColumn.Name].LineageID,
                                DTSUsageType.UT_IGNORED);
                    }
                }

                #endregion
            }
        }

        private static void CreateInputCollectionAndMapByName(IDTSDesigntimeComponent100 designTimeComponent, IDTSInput100 input, IDTSVirtualInput100 virutalInput, IDTSVirtualInputColumnCollection100 virtualInputColumnCollection)
        {
            for (int externalColumnIndex = 0; externalColumnIndex < input.ExternalMetadataColumnCollection.Count;
                externalColumnIndex++)
            {
                for (int inputColumnIndex = 0; inputColumnIndex < virtualInputColumnCollection.Count; inputColumnIndex++)
                {
                    if (virtualInputColumnCollection[inputColumnIndex].Name
                        == input.ExternalMetadataColumnCollection[externalColumnIndex].Name)
                    {
                        IDTSInputColumn100 iColumn = designTimeComponent.SetUsageType(input.ID, virutalInput,
                            virtualInputColumnCollection[inputColumnIndex].LineageID, DTSUsageType.UT_READONLY);

                        designTimeComponent.MapInputColumn(input.ID, iColumn.ID,
                            input.ExternalMetadataColumnCollection[externalColumnIndex].ID);

                        break;
                    }
                }
            }
        }

        #endregion
    }
}
